Namespace("sketch.framework");

(function(fwk) {
    var cmd = sketch.commands;

    var SNAP_DELTA = 4;
    var LINE_OFFSET = 10;
    fwk.DraggingElement = klass(fwk.UIElement, (function() {
        var findSnap = function(snap, value, delta) {
            for (var j = 0, len2 = snap.length; j < len2; ++j) {//TODO:replace with binary search
                if (Math.abs(value - snap[j].value) <= delta) {
                    return snap[j];
                }
            }

            return null;
        };

        var applySnapping = function(pos) {
            var xs = this._xs,
                ys = this._ys,
                snapX = this._snapX,
                snapY = this._snapY,
                snapXCenter = this._snapXCenter,
                snapYCenter = this._snapYCenter;
            var snap;
            this._activeSnapVertical = [];
            this._activeSnapHorizontal = [];

            var delta = SNAP_DELTA;
            snap = findSnap(snapXCenter, this._xCenter + pos.x, delta);
            if (snap != null) {
                pos.x = snap.value - this._xCenter;
                this._activeSnapVertical.push(snap);
            } else {
                for (var i = 0, len = xs.length; i < len; ++i) {
                    snap = findSnap(snapX, xs[i] + pos.x, delta);
                    if (snap != null) {
                        pos.x = snap.value - xs[i];
                        this._activeSnapVertical.push(snap);
                        delta = 0;
                    }
                }
            }

            delta = SNAP_DELTA;
            snap = findSnap(snapYCenter, this._yCenter + pos.y, delta);
            if (snap != null) {
                pos.y = snap.value - this._yCenter;
                this._activeSnapHorizontal.push(snap);
            } else {
                for (i = 0,len = ys.length; i < len; ++i) {
                    snap = findSnap(snapY, ys[i] + pos.y, delta);
                    if (snap != null) {
                        pos.y = snap.value - ys[i];
                        this._activeSnapHorizontal.push(snap);
                        delta = 0;
                    }
                }
            }

            return pos;
        };

        var applySnappingForPoint = function(pos) {
            var snapX = this._snapX,
                snapY = this._snapY;
            var snap;

            var delta = SNAP_DELTA;
            snap = findSnap(snapX, pos.x, delta);
            if (snap != null) {
                pos.x = snap.value;
                this._activeSnapVertical.push(snap);
            }

            delta = SNAP_DELTA;
            snap = findSnap(snapY, pos.y, delta);
            if (snap != null) {
                pos.y = snap.value;
                this._activeSnapHorizontal.push(snap);
            }

            return pos;
        };

        var calculateSnappingPoints = function() {
            var view = this._element.view();
            var layer = view.layer2;
            var that = this;
            this._snapX = [];
            this._snapY = [];
            this._snapXCenter = [];
            this._snapYCenter = [];
            this._activeSnapVertical = [];
            this._activeSnapHorizontal = [];
            layer.applyVisitor(function(element) {
                if (element != that._element) {
                    var snapData = element.getSnapPoints();
                    if (snapData !== null) {
                        var xs = snapData.xs;
                        var ys = snapData.ys;
                        for (var i = 0, length = xs.length; i < length; ++ i) {
                            that._snapX.push({value:xs[i], element:element});
                        }

                        for (i = 0,length = ys.length; i < length; ++ i) {
                            that._snapY.push({value:ys[i], element:element});
                        }
                        that._snapXCenter.push({value:snapData.center.x, element:element});
                        that._snapYCenter.push({value:snapData.center.y, element:element});
                    }
                }
            });
            this._snapX.sort(function(x1, x2) {
                return x1.value - x2.value;
            });
            this._snapY.sort(function(x1, x2) {
                return x1.value - x2.value;
            });
        };

        var prepareOwnSnapPoints = function() {
            var snapData = this._element.getSnapPoints()
            if (snapData !== null) {
                this._xs = snapData.xs;
                this._ys = snapData.ys;
                this._xCenter = snapData.center.x - this.left();
                this._yCenter = snapData.center.y - this.top();

                for (var i = 0; i < this._xs.length; i++) {
                    this._xs[i] -= this.left();
                }
                for (i = 0; i < this._xs.length; i++) {
                    this._ys[i] -= this.top();
                }
                this._xs.sort();
                this._ys.sort();
            } else {
                this._snappingDataAvailiable = false;
            }
        };

        var drawSnapLines = function(context) {
            var scale = this.view().scale();
            for (var i = 0; i < this._activeSnapVertical.length; i++) {
                var snap = this._activeSnapVertical[i];
                var rect = snap.element.getBoundaryRectGlobal();
                var ys = [rect.y, rect.y + rect.height, this.top(), this.top() + this.height()];
                ys.sort(function(v1, v2) {
                    return v1 - v2;
                });
                context.strokeLine(snap.value, ys[0] - LINE_OFFSET, snap.value, ys[3] + LINE_OFFSET, 'rgba(100, 100, 100, 0.5)', 2 / scale);
            }
            for (i = 0; i < this._activeSnapHorizontal.length; i++) {
                snap = this._activeSnapHorizontal[i];
                rect = snap.element.getBoundaryRectGlobal();
                var xs = [rect.x, rect.x + rect.width, this.left(), this.left() + this.width()];
                xs.sort(function(v1, v2) {
                    return v1 - v2;
                });
                context.strokeLine(xs[0] - LINE_OFFSET, snap.value, xs[3] + LINE_OFFSET, snap.value, 'rgba(100, 100, 100, 0.5)', 2 / scale);
            }
        };

        return {
            __type__:"framework.DraggingElement",
            _constructor:function(element) {
                this._element = element;

                this._snappingDataAvailiable = true;


                var pos = element.position();
                pos = element.parent().local2global(pos);
                this.init({
                    width:element.width(),
                    height:element.height(),
                    left:pos.x,
                    top:pos.y,
                    opacity:0.7
                });

                calculateSnappingPoints.call(this);
                prepareOwnSnapPoints.call(this);
            },
            drawSelf:function(context) {
                var x = this.left(),
                    y = this.top(),
                    w = this.width(),
                    h = this.height();
                context.save();
                context.fillStyle = 'rgba(160, 180, 200, 0.1)';
                context.fillRect(x, y, w, h);
                context.restore();
                context.save();

                context.translate(-this._element.left() + x, -this._element.top() + y);
                if (this._element.clipSelf()) {
                    context.rectPath(x, y, w, h);
                    context.clip();
                }
                var oldSize = this._element.getBoundaryRect(); // TODO: resize if resize
                this._element.lockInvalidate();
                this._element.resize({x:oldSize.x, y:oldSize.y, width:w, height:h});
                this._element.drawSelf(context);
                this._element.resize(oldSize);
                this._element.unlockInvalidate();

                context.restore();
                drawSnapLines.call(this, context);
            },
            element:function() {
                return this._element;
            },
            detach:function(event, newParent) {
                this.parent().remove(this);
                var pos = newParent.global2local(this.position());
                var parent = this._element.parent();
                var index = undefined;
                if (event) {
                    var dropData = newParent.getDropData({x:event.x, y:event.y}, this._element);
                    if (dropData !== null) {
                        index = dropData.index;
                    }
                }

                var that = this;

                if (this._element instanceof fwk.SelectComposite) {
                    var commands = [];

                    pos.x -= this._element.left();
                    pos.y -= this._element.top();

                    this._element.each(function(element) {
                        var width = element.width(),
                            height = element.height();
                        var elementGlobal = element.parent().local2global(element.position());

                        commands.push(new sketch.commands.MoveResizeElement(
                            element,
                            {x:pos.x + elementGlobal.x, y:pos.y + elementGlobal.y, width:width, height:height},
                            newParent,
                            index));
                    });

                    fwk.commandManager.execute(new sketch.commands.Composite(commands, this._element.view()));
                } else {
                    fwk.commandManager.execute(new sketch.commands.MoveResizeElement(
                        this._element,
                        {x:pos.x, y:pos.y, width:this.width(), height:this.height()},
                        newParent,
                        index));
                }
            },
            dragTo:function(event) {
                var position = event;
                if (this._snappingDataAvailiable && this.parentAllowSnapping(event)) {
                    position = applySnapping.call(this, position);
                } else {
                    this._activeSnapVertical = [];
                    this._activeSnapHorizontal = [];
                }
                fwk.DraggingElement.Super.dragTo.call(this, position);
            },
            parentAllowSnapping:function(pos) {
                var dragData = this._element.parent().getDropData(this._element, pos);
                return dragData === null;
            },
            resize:function(/*Rect*/rect) {
                if (this._snappingDataAvailiable && this.parentAllowSnapping({x:0, y:0})) {
                    this._activeSnapVertical = [];
                    this._activeSnapHorizontal = [];
                    var boundary = this._element.getBoundaryRectGlobal();

                    var pos = {x:Number.MAX_VALUE, y:Number.MAX_VALUE};
                    if (boundary.x !== rect.x) {
                        pos.x = rect.x;
                    }
                    if (boundary.y !== rect.y) {
                        pos.y = rect.y;
                    }
                    pos = applySnappingForPoint.call(this, pos);

                    var pos2 = {x:Number.MAX_VALUE, y:Number.MAX_VALUE};
                    if (boundary.x + boundary.width !== rect.x + rect.width) {
                        pos2.x = rect.x + rect.width;
                    }
                    if (boundary.y + boundary.height !== rect.y + rect.height) {
                        pos2.y = rect.y + rect.height;
                    }
                    pos2 = applySnappingForPoint.call(this, pos2);

                    if (pos2.x !== Number.MAX_VALUE) {
                        rect.width = Math.abs(pos2.x - rect.x);
                    }
                    if (pos2.y !== Number.MAX_VALUE) {
                        rect.height = Math.abs(pos2.y - rect.y);
                    }
                    if (pos.x !== Number.MAX_VALUE) {
                        rect.x = pos.x;
                        rect.width = Math.abs(boundary.x + boundary.width - rect.x);
                    }
                    if (pos.y !== Number.MAX_VALUE) {
                        rect.y = pos.y
                        rect.height = Math.abs(boundary.y + boundary.height - rect.y);
                    }
                } else {
                    this._activeSnapVertical = [];
                    this._activeSnapHorizontal = [];
                }

                fwk.DraggingElement.Super.resize.call(this, rect);
            }
        };
    })());
})(sketch.framework);